package com.medord.machine.core;

import com.medord.machine.core.annotition.StateService;
import com.medord.machine.core.annotition.TargetStateMethod;
import com.medord.machine.core.entity.FlowResult;
import com.medord.machine.core.entity.ICurrentStateDto;
import com.medord.machine.utils.FlowSpringContextHolder;
import lombok.SneakyThrows;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StopWatch;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.*;

/**
 * @author 孙丰佳
 * @version V1.0
 * @Title: Context
 * @Package com.medord.machine.core
 * @Description: (用一句话描述该文件做什么)
 * @date 2021-08-01 15:32
 **/
@Component
public class FlowContext implements BeanPostProcessor, IFlowContext {
    //用于cache中key值的分隔符
    private static final String SPLITER = "丨";

    //状态流程Map(key:处理的当前状态。value:处理器对象)
    //添加多流程适配：key作为"流程String丨当前状态"，名称cache同理
    private Map<String, FlowState> flowCache;

    //状态对应名称(key:当前状态。value:提示中的名称)
    private Map<String, String> flowDesCache;

    private ExecutorService executorPool;

    /**
     * 初始化环境类：
     * 1、当IOC中bean初始化完毕，可以从IOC中拿到bean时，拿到所有继承自FlowState的流程bean.
     * 2、构建bean的cache，用于分发。
     * 3、构建名称的cache，用于提示。
     * 4、初始化连接池。
     * @param bean
     * @param beanName
     * @return java.lang.Object
     * @author 孙丰佳
     * @time 2021-11-23 15:08
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("flowSpringContextHolder")) {
            //初始化线程池
            int coreNum = Runtime.getRuntime().availableProcessors();
            this.executorPool = new ThreadPoolExecutor(coreNum, coreNum,
                    10L, TimeUnit.MINUTES,
                    new LinkedBlockingQueue<>(10),
                    Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.AbortPolicy()
            );

            //初始化状态处理器的缓存
            ApplicationContext applicationContext;
            try {
                applicationContext = (ApplicationContext) bean.getClass().getDeclaredMethod("getApplicationContext").invoke(bean);
            } catch (Exception e) {
                throw new RuntimeException("Context初始化失败：" + e.getMessage());
            }
            Map<String, FlowState> flowStateMap = applicationContext.getBeansOfType(FlowState.class);
            Set<Map.Entry<String, FlowState>> entrySet = flowStateMap.entrySet();

            this.flowCache = new HashMap<>();
            this.flowDesCache = new HashMap<>();

            for (Map.Entry<String, FlowState> tempSet : entrySet) {
                FlowState flowState = tempSet.getValue();
                StateService stateServiceAnno = this.getStateServiceAnno(flowState);
                if (ObjectUtils.isEmpty(stateServiceAnno)) {
                    continue;
                }
                String state = stateServiceAnno.state();
                String flowName = stateServiceAnno.flowName();
                String stateDes = ObjectUtils.isEmpty(stateServiceAnno.stateDes()) ? state : stateServiceAnno.stateDes();
                this.flowCache.put(this.getKey(flowName, state), flowState);
                this.flowDesCache.put(this.getKey(flowName, state), stateDes);
            }
        }
        return bean;
    }


    /**
     * 执行状态变化的主逻辑：
     * 1、遍历需要进行状态变化的实体List。
     * 2、获取相应状态的处理类
     * 3、获取该处理类所有方法，由注解TargetStateMethod查看是否支持该状态的转换。
     *        注：由于可能出现代理，所以拿到被代理类的方法才能拿到注解
     * 4、如果找不到相应状态变化方法，给出统一提示。
     * 5、调用状态变更方法，控制事务回滚，统一将异常信息加入error提示。(注：一个对象抛异常，记录报错原因返回，并continue下一个)
     *
     * @param currentStateDtoList 需要变状态的List
     * @param targetState 想转变成的目标状态
     * @param para
     * @return com.medord.machine.core.entity.FlowResult
     * @author 孙丰佳
     * @time 2021-09-17 11:13
     */
    @SneakyThrows
    @Override
    public FlowResult forwordState(String flowName, List<ICurrentStateDto> currentStateDtoList, String targetState, Map para) {
        FlowResult flowResult = new FlowResult();

        //是否有目标状态的flowState子类（处理器），没有就报错
        if (ObjectUtils.isEmpty(this.flowDesCache.get(this.getKey(flowName, targetState)))) {
            flowResult.addError("没有目标状态的处理器，请联系管理员！");
            return flowResult;
        }

        //开启多线程，同时开启多个状态变更流程。
        //在这里需要注意，如果同时变更的数据，有依赖同批次数据状态控制的，有可能会出错,需要择出来。
        //eg：
        // a{status:1} b{status:10}
        // 想要a.status从1->2，同时b.status从10->2，而a的变化需要其他数据状态!=10才能变更
        // 此时由于多线程，事务并未提交，a流程中，可能幻读b.status==10，从而导致不能变更a，
        // 而实际上b变化完应该是2，a是可以变更的，此时就会导致业务数据错误。
        //所以，在调用forwordState前，需要在业务代码上做出一些数据之间状态排斥的判断及验证.
        CountDownLatch cdl = new CountDownLatch(currentStateDtoList.size());
        Map<ICurrentStateDto, Future<FlowResult>> futureMap = new HashMap<>();

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        for (ICurrentStateDto iCurrentStateDto : currentStateDtoList) {
            FlowState flowState = null;
            try {
                flowState = this.getFlowState(flowName, iCurrentStateDto);
            } catch (Exception e) {
                flowResult.addError(e.getMessage());
                cdl.countDown();
                continue;
            }
            Method targetMethod = null;
            Method[] declaredMethods;
            if (AopUtils.isCglibProxy(flowState) || AopUtils.isJdkDynamicProxy(flowState)) {
                Class<?> targetClass = AopUtils.getTargetClass(flowState);
                declaredMethods = targetClass.getDeclaredMethods();
            } else {
                declaredMethods = flowState.getClass().getDeclaredMethods();
            }
            for (Method declaredMethod : declaredMethods) {
                TargetStateMethod targetStateAnno = declaredMethod.getAnnotation(TargetStateMethod.class);
                if (ObjectUtils.isEmpty(targetStateAnno)) {
                    continue;
                }
                if (ObjectUtils.isEmpty(targetStateAnno.target())) {
                    continue;
                }
                if (!targetStateAnno.target().equals(targetState)) {
                    continue;
                }
                targetMethod = declaredMethod;
                break;
            }

            if (ObjectUtils.isEmpty(targetMethod)) {
                flowResult.addError("【" + iCurrentStateDto.getName() + "】" +
                        "状态为【" + this.flowDesCache.get(this.getKey(flowName, iCurrentStateDto.getState())) + "】，" +
                        "此状态不能转变为【" + this.flowDesCache.get(this.getKey(flowName, targetState)) + "】");
                cdl.countDown();
                continue;
            }


            FlowContext contextBean = FlowSpringContextHolder.getBean(FlowContext.class);

            final Map FINAL_PARA = para;
            final Method FINAL_METHOD = targetMethod;
            final FlowState FINAL_FLOW_STATE = flowState;
            Future<FlowResult> future = executorPool.submit(() -> {
                try {
                    IFlowProcessParameter flowProcessParameter = new FlowProcessParameter(FINAL_PARA);
                    FlowContextUtil.setProcessParameter(flowProcessParameter);
                    FlowResult finalResult = contextBean.executeChange(flowName, iCurrentStateDto, FINAL_METHOD, FINAL_FLOW_STATE, targetState);
                    return finalResult;
                } catch (Exception e) {
                    throw new RuntimeException(e.getMessage());
                } finally {
                    cdl.countDown();
                    FlowContextUtil.clear();
                }
            });
            futureMap.put(iCurrentStateDto, future);
        }

        cdl.await();
        if (!ObjectUtils.isEmpty(futureMap)) {
            for (Map.Entry<ICurrentStateDto, Future<FlowResult>> entry : futureMap.entrySet()) {
                ICurrentStateDto key = entry.getKey();
                FlowResult savedflowResult;
                try {
                    Future<FlowResult> future = entry.getValue();
                    savedflowResult = future.get();
                } catch (Exception e) {
                    String message = e.getCause().getMessage();
                    flowResult.addError("【" + key.getName() + "】" + "变更失败：" + message);
                    continue;
                }
                flowResult.copyData(savedflowResult);
            }
        }

        stopWatch.stop();
        flowResult.addSuccessMsg(String.valueOf(stopWatch.getTotalTimeMillis()));
        return flowResult;
    }

    /**
     * 状态变更流程:
     * 1、执行前置切面方法。
     * 2、执行处理类状态变更方法
     * 3、执行后置切面方法。
     * @param iCurrentStateDto
     * @param targetMethod
     * @param flowState
     * @param targetState
     * @return void
     * @author 孙丰佳
     * @time 2021-11-11 15:41
     */
    @Transactional
    public FlowResult executeChange(String flowName,
                                    ICurrentStateDto iCurrentStateDto,
                                    Method targetMethod,
                                    FlowState flowState,
                                    String targetState) {
        //-- 执行改变状态的方法 开始 -----------------------------------------------
        String resultMsg;
        try {
            resultMsg = (String) targetMethod.invoke(flowState, iCurrentStateDto);
        } catch (Exception e) {
            throw new RuntimeException(e.getCause().getMessage());
        }

        FlowProcessParameter parameter = (FlowProcessParameter) FlowContextUtil.getProcessParameter();
        if (ObjectUtils.isEmpty(resultMsg)) {
            parameter.addSuccessMsg("【" + iCurrentStateDto.getName() + "】状态变化为【" + this.flowDesCache.get(this.getKey(flowName, targetState)) + "】成功");
        } else {
            parameter.addSuccessMsg(resultMsg);
        }
        FlowResult savedflowResult = parameter.getFlowResult();
        return savedflowResult;
        //-- 执行改变状态的方法 结束 -----------------------------------------------
    }

    private StateService getStateServiceAnno(FlowState flow) {
        Class<? extends FlowState> flowClass = flow.getClass();
        if (AopUtils.isAopProxy(flow)) {
            flowClass = (Class<? extends FlowState>) AopProxyUtils.ultimateTargetClass(flow);
        }
        StateService annotation = flowClass.getAnnotation(StateService.class);
        return annotation;
    }

    private FlowState getFlowState(String flowName, ICurrentStateDto iCurrentStateDto) throws Exception {
        FlowState stateFlow = this.flowCache.get(this.getKey(flowName, iCurrentStateDto.getState()));
        if (ObjectUtils.isEmpty(stateFlow)) {
            throw new Exception("容器中没有状态为【" + iCurrentStateDto.getState() + "】的处理器, 无法将【" + iCurrentStateDto.getName() + "】当前状态进行变更！");
        }
        return stateFlow;
    }

    private String getKey(String flowName, String state) {
        return flowName + FlowContext.SPLITER + state;
    }

    /**
     * 继承自IFlowProcessParameter的内部类，对外隐藏实现代码
     * 屏蔽不打算让外部使用的方法
     * @return
     * @author 孙丰佳
     * @time 2021-11-18 17:26
     */
    private class FlowProcessParameter implements IFlowProcessParameter{
        private final String OUTER_PARA = "outer_para";
        private final String INNER_PARA = "inner_para";

        private FlowResult flowResult = new FlowResult();

        private Map<String, Map> para = new HashMap<>();

        private FlowProcessParameter(Map _para){
            this.para.put(INNER_PARA, new HashMap());
            this.para.put(OUTER_PARA, ObjectUtils.isEmpty(_para) ? new HashMap() : _para);
        }

        private FlowResult getFlowResult() {
            return this.flowResult;
        }

        private void addSuccessMsg(String msg) {
            this.flowResult.addSuccessMsg(msg);
        }

        //-- 以下是对外（对FlowState）暴露的方法 -------------------------------------------------
        @Override
        public void putInnerPara(Object key, Object value) {
            this.para.get(INNER_PARA).put(key, value);
        }

        @Override
        public Object getInnerPara(Object key) {
            return this.para.get(INNER_PARA).get(key);
        }

        @Override
        public Object getOuterPara(Object key) {
            return this.para.get(OUTER_PARA).get(key);
        }

        @Override
        public void addWarningMsg(String msg) {
            this.flowResult.addWarning(msg);
        }
    }

}
